﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks.Dataflow;

namespace QuantBox.XApi
{
    internal class QueryManager
    {
        private static readonly Encoding Gb2312 = Encoding.GetEncoding(936);
        private readonly UfxClient _client;
        private const string LocalIdFormat = "D8";
        private const string UfxClient = "UFX";

        private struct QueryEvent
        {
            public readonly QueryType QueryType;
            public readonly bool Request;
            public readonly int FunctionId;
            public readonly object Response;

            public QueryEvent(QueryType type)
            {
                Request = true;
                QueryType = type;
                FunctionId = -1;
                Response = null;
            }

            public QueryEvent(int functionId, object rsp)
            {
                Request = false;
                QueryType = QueryType.ReqQryInstrument;
                FunctionId = functionId;
                Response = rsp;
            }
        }

        private readonly Queue<IUfxFunctionCall> _functionPending = new Queue<IUfxFunctionCall>();
        private readonly Queue<IUfxFunctionCall> _functionRunning = new Queue<IUfxFunctionCall>();

        private readonly Queue<QueryType> _requestQueue = new Queue<QueryType>();
        private readonly ActionBlock<QueryEvent> _action;
        private AccountField _accountField;

        private bool QueryCompleted() => _functionRunning.Count == 0 && _functionPending.Count == 0;

        private void ProcessQuery()
        {
            if (_requestQueue.Count <= 0) {
                return;
            }

            var query = _requestQueue.Dequeue();
            var account = _client.AccountInfo;
            switch (query) {
                case QueryType.ReqQryInstrument:
                    //_client.Spi.ProcessQryInstrument(null, true);
                    _functionPending.Enqueue(UfxFuturesFunction.Make30010(account.UserToken));
                    break;
                case QueryType.ReqQryInvestorPosition:
                    _functionPending.Enqueue(UfxSecuritiesFunction.Make31001(account.UserToken)
                        .SetAccountCode(account.AccountCode)
                        .SetAssetNo(account.AssetNo)
                        .SetCombiNo(account.CombinationNo));
                    _functionPending.Enqueue(UfxFuturesFunction.Make31003(account.UserToken)
                        .SetAccountCode(account.AccountCode)
                        .SetAssetNo(account.AssetNo)
                        .SetCombiNo(account.CombinationNo));
                    _functionPending.Enqueue(UfxOptionsFunction.Make31004(account.UserToken)
                        .SetAccountCode(account.AccountCode)
                        .SetAssetNo(account.AssetNo)
                        .SetCombiNo(account.CombinationNo));
                    break;
                case QueryType.ReqQryTradingAccount:
                    _functionPending.Enqueue(UfxAccountFunction.Make35024(account.UserToken, account.AccountCode)
                        .SetAssetNo(account.AssetNo));
                    _functionPending.Enqueue(UfxAccountFunction.Make35025(account.UserToken, account.AccountCode, account.AssetNo));
                    _functionPending.Enqueue(UfxFuturesFunction.Make34003(account.UserToken)
                        .SetAccountCode(account.AccountCode)
                        .SetAssetNo(account.AssetNo)
                        .SetCombiNo(account.CombinationNo));
                    _functionPending.Enqueue(UfxOptionsFunction.Make34004(account.UserToken, UfxMarketNo.V1)
                        .SetAccountCode(account.AccountCode)
                        .SetAssetNo(account.AssetNo)
                        .SetCombiNo(account.CombinationNo));
                    _functionPending.Enqueue(UfxOptionsFunction.Make34004(account.UserToken, UfxMarketNo.V2)
                        .SetAccountCode(account.AccountCode)
                        .SetAssetNo(account.AssetNo)
                        .SetCombiNo(account.CombinationNo));
                    break;
                case QueryType.ReqQryOrder:
                    _functionPending.Enqueue(UfxSecuritiesFunction.Make32001(account.UserToken)
                        .SetAccountCode(account.AccountCode)
                        .SetAssetNo(account.AssetNo)
                        .SetCombiNo(account.CombinationNo));
                    _functionPending.Enqueue(UfxFuturesFunction.Make32003(account.UserToken)
                        .SetAccountCode(account.AccountCode)
                        .SetAssetNo(account.AssetNo)
                        .SetCombiNo(account.CombinationNo));
                    _functionPending.Enqueue(UfxOptionsFunction.Make32004(account.UserToken)
                        .SetAccountCode(account.AccountCode)
                        .SetAssetNo(account.AssetNo)
                        .SetCombiNo(account.CombinationNo));
                    break;
                case QueryType.ReqQryTrade:
                    _functionPending.Enqueue(UfxSecuritiesFunction.Make33001(account.UserToken)
                        .SetAccountCode(account.AccountCode)
                        .SetAssetNo(account.AssetNo)
                        .SetCombiNo(account.CombinationNo));
                    _functionPending.Enqueue(UfxFuturesFunction.Make33003(account.UserToken)
                        .SetAccountCode(account.AccountCode)
                        .SetAssetNo(account.AssetNo)
                        .SetCombiNo(account.CombinationNo));
                    _functionPending.Enqueue(UfxOptionsFunction.Make33004(account.UserToken)
                        .SetAccountCode(account.AccountCode)
                        .SetAssetNo(account.AssetNo)
                        .SetCombiNo(account.CombinationNo));
                    break;
            }
            ProcessFunctionPending();
        }

        private void ProcessFunctionPending()
        {
            if (_functionPending.Count > 0) {
                var function = _functionPending.Dequeue();
                _functionRunning.Enqueue(function);
                _client.AsyncCall(function);
            }
        }

        private void Process30010(Ufx30010Result[] results)
        {
            if (results.Length > 0) {
                for (int i = 0; i < results.Length; i++) {
                    var item = results[i];
                    var last = i == results.Length - 1 && QueryCompleted();
                    var inst = new InstrumentField();
                    inst.InstrumentID = item.StockCode;
                    inst.Symbol = item.StockCode;
                    inst.ExchangeID = UfxConvert.GetExchange(item.MarketNo);
                    inst.PriceTick = item.PriceInterval;
                    inst.ExchangeInstID = item.StockCode;
                    inst.InstLifePhase = InstLifePhaseType.Started;
                    inst.InstrumentName = Gb2312.GetBytes(item.StockName);
                    inst.ProductID = item.FutureKindName;
                    inst.ExpireDate = item.LastTradeDate;
                    inst.Type = InstrumentType.Future;
                    inst.VolumeMultiple = item.Multiple;
                    _client.Spi.ProcessQryInstrument(inst, last);
                }
            }
            else {
                _client.Spi.ProcessQryInstrument(null, true);
            }
        }

        #region Account Response
        private void Process35024(Ufx35024Result[] results)
        {
            if (results != null && results.Length > 0) {
                var data = results.First();
                var account = _accountField ?? new AccountField();
                account.AccountID = data.AccountCode;
                account.ClientID = data.AssetNo;
                account.PreBalance = data.BeginBalance;
                account.Available = data.CurrentBalance;
                _accountField = account;
            }
            SendAccount();
        }

        private void Process35025(Ufx35025Result[] results)
        {
            if (results != null && results.Length > 0) {
                var data = results.First();
                var account = _accountField ?? new AccountField();
                account.PositionProfit = data.FutuFloatProfit;
                account.CloseProfit = data.FutuCloseProfit;
                _accountField = account;
            }

            SendAccount();
        }

        private void Process34003(Ufx34003Result[] results)
        {
            if (results != null && results.Length > 0) {
                var data = results.First();
                var account = _accountField ?? new AccountField();
                account.CurrMargin += data.OccupyDepositBalance;
                _accountField = account;
            }

            SendAccount();
        }

        private void Process34004(Ufx34004Result[] results)
        {
            if (results != null && results.Length > 0) {
                var data = results.First();
                var account = _accountField ?? new AccountField();
                account.CurrMargin += data.OccupyDepositBalance;
                _accountField = account;
            }

            SendAccount();
        }

        private void SendAccount()
        {
            if (_accountField != null && QueryCompleted()) {
                _client.Spi.ProcessQryAccount(_accountField, true);
            }
        }
        #endregion

        #region Position Response
        private void Process31001(Ufx31001Result[] results)
        {
            if (results.Length > 0) {
                for (int i = 0; i < results.Length; i++) {
                    var item = results[i];
                    var last = i == results.Length - 1 && QueryCompleted();
                    var position = new PositionField();
                    position.AccountID = item.AccountCode;
                    position.ClientID = item.AssetNo;
                    position.InstrumentID = item.StockCode;
                    position.ExchangeID = UfxConvert.GetExchange(item.MarketNo);
                    position.Side = PositionSide.Long;
                    position.Date = DateTime.Today.ToIntDate();
                    position.TodayPosition = item.TodayBuyAmount;
                    position.Position = item.CurrentAmount;
                    position.HistoryPosition = item.CurrentAmount - item.TodayBuyAmount;
                    position.PositionCost = item.CurrentCost;
                    _client.Spi.ProcessQryPosition(position, last);
                }
            }
            else {
                _client.Spi.ProcessQryPosition(null, true);
            }
        }
        private void Process31003(Ufx31003Result[] results)
        {
            if (results.Length > 0) {
                for (int i = 0; i < results.Length; i++) {
                    var item = results[i];
                    var last = i == results.Length - 1 && QueryCompleted();
                    var position = new PositionField();
                    position.AccountID = item.AccountCode;
                    position.ClientID = item.AssetNo;
                    position.InstrumentID = item.StockCode;
                    position.ExchangeID = UfxConvert.GetExchange(item.MarketNo);
                    position.Side = UfxConvert.GetPositionSide(item.PositionFlag);
                    position.Date = DateTime.Today.ToIntDate();
                    position.TodayPosition = item.TodayAmount;
                    position.Position = item.CurrentAmount;
                    position.HistoryPosition = item.LastdayAmount;
                    position.PositionCost = item.CurrentCost;
                    _client.Spi.ProcessQryPosition(position, last);
                }
            }
            else {
                _client.Spi.ProcessQryPosition(null, true);
            }
        }
        private void Process31004(Ufx31004Result[] results)
        {
            if (results.Length > 0) {
                for (int i = 0; i < results.Length; i++) {
                    var item = results[i];
                    var last = i == results.Length - 1 && QueryCompleted();
                    var position = new PositionField();
                    position.AccountID = item.AccountCode;
                    position.ClientID = item.AssetNo;
                    position.InstrumentID = item.StockCode;
                    position.ExchangeID = UfxConvert.GetExchange(item.MarketNo);
                    position.Side = UfxConvert.GetPositionSide(item.PositionFlag);
                    position.Date = DateTime.Today.ToIntDate();
                    position.TodayPosition = item.TodayAmount;
                    position.Position = item.CurrentAmount;
                    position.HistoryPosition = item.LastdayAmount;
                    position.PositionCost = item.CurrentCost;
                    _client.Spi.ProcessQryPosition(position, last);
                }
            }
            else {
                _client.Spi.ProcessQryPosition(null, true);
            }
        }
        #endregion

        #region Order Response
        private void Process32001(Ufx32001Result[] results)
        {
            if (results.Length > 0) {
                for (int i = 0; i < results.Length; i++) {
                    var item = results[i];
                    var last = i == results.Length - 1 && QueryCompleted();
                    var order = new OrderField();
                    order.AccountID = item.AccountCode;
                    order.ClientID = item.AssetNo;
                    order.InstrumentID = item.StockCode;
                    order.Symbol = item.StockCode;
                    order.ExchangeID = UfxConvert.GetExchange(item.MarketNo);
                    order.Date = item.EntrustDate;
                    order.Time = item.EntrustTime;
                    order.OrderID = item.EntrustNo.ToString();
                    order.LocalID = item.ExtsystemId.ToString(LocalIdFormat);
                    order.ID = IdGenerator.GetIdFromThirdRef(item.ThirdReff);
                    order.Side = UfxConvert.GetOrderSide(item.EntrustDirection);
                    order.Type = UfxConvert.GetOrderType(item.PriceType);
                    order.OpenClose = order.Side == OrderSide.Buy ? OpenCloseType.Open : OpenCloseType.Close;
                    order.Price = item.EntrustPrice;
                    order.Qty = item.EntrustAmount;
                    order.CumQty = item.DealAmount;
                    order.LeavesQty = item.EntrustAmount - item.DealAmount;
                    order.AvgPx = item.DealPrice;
                    order.TimeInForce = TimeInForce.GTD;
                    order.HedgeFlag = HedgeFlagType.Speculation;
                    order.Status = UfxConvert.GetOrderStatus(item.EntrustState);
                    order.ReserveChar64 = UfxClient;
                    _client.Spi.ProcessQryOrder(order, last);
                }
            }
            else {
                _client.Spi.ProcessQryOrder(null, true);
            }
        }
        private void Process32003(Ufx32003Result[] results)
        {
            if (results.Length > 0) {
                for (int i = 0; i < results.Length; i++) {
                    var item = results[i];
                    var last = i == results.Length - 1 && QueryCompleted();
                    var order = new OrderField();
                    order.AccountID = item.AccountCode;
                    order.ClientID = item.AssetNo;
                    order.InstrumentID = item.StockCode;
                    order.Symbol = item.StockCode;
                    order.ExchangeID = UfxConvert.GetExchange(item.MarketNo);
                    order.Date = item.EntrustDate;
                    order.Time = item.EntrustTime;
                    order.OrderID = item.EntrustNo.ToString();
                    order.LocalID = item.ExtsystemId.ToString(LocalIdFormat);
                    order.ID = IdGenerator.GetIdFromThirdRef(item.ThirdReff);
                    order.Side = UfxConvert.GetOrderSide(item.EntrustDirection);
                    order.Type = UfxConvert.GetOrderType(item.PriceType);
                    order.OpenClose = UfxConvert.GetOpenClose(item.MarketNo, item.FuturesDirection, item.CloseDirection);
                    order.Price = item.EntrustPrice;
                    order.Qty = item.EntrustAmount;
                    order.CumQty = item.DealAmount;
                    order.LeavesQty = item.EntrustAmount - item.DealAmount;
                    order.AvgPx = item.DealPrice;
                    order.TimeInForce = TimeInForce.GTD;
                    order.HedgeFlag = HedgeFlagType.Speculation;
                    order.Status = UfxConvert.GetOrderStatus(item.EntrustState);
                    order.ReserveChar64 = UfxClient;
                    _client.Spi.ProcessQryOrder(order, last);
                }
            }
            else {
                _client.Spi.ProcessQryOrder(null, true);
            }
        }
        private void Process32004(Ufx32004Result[] results)
        {
            if (results.Length > 0) {
                for (int i = 0; i < results.Length; i++) {
                    var item = results[i];
                    var last = i == results.Length - 1 && QueryCompleted();
                    var order = new OrderField();
                    order.AccountID = item.AccountCode;
                    order.ClientID = item.AssetNo;
                    order.InstrumentID = item.StockCode;
                    order.Symbol = item.StockCode;
                    order.ExchangeID = UfxConvert.GetExchange(item.MarketNo);
                    order.Date = item.EntrustDate;
                    order.Time = item.EntrustTime;
                    order.OrderID = item.EntrustNo.ToString();
                    order.LocalID = item.ExtsystemId.ToString();
                    order.ID = IdGenerator.GetIdFromThirdRef(item.ThirdReff);
                    order.Side = UfxConvert.GetOrderSide(item.EntrustDirection);
                    order.Type = UfxConvert.GetOrderType(item.PriceType);
                    order.OpenClose = UfxConvert.GetOpenClose(item.FuturesDirection);
                    order.Price = item.EntrustPrice;
                    order.Qty = item.EntrustAmount;
                    order.CumQty = item.DealAmount;
                    order.LeavesQty = item.EntrustAmount - item.DealAmount;
                    order.AvgPx = item.DealPrice;
                    order.TimeInForce = TimeInForce.GTD;
                    order.HedgeFlag = HedgeFlagType.Speculation;
                    order.Status = UfxConvert.GetOrderStatus(item.EntrustState);
                    order.ReserveChar64 = UfxClient;
                    _client.Spi.ProcessQryOrder(order, last);
                }
            }
            else {
                _client.Spi.ProcessQryOrder(null, true);
            }
        }
        #endregion

        #region Trade Response
        private void Process33001(Ufx33001Result[] results)
        {
            if (results.Length > 0) {
                for (int i = 0; i < results.Length; i++) {
                    var item = results[i];
                    var last = i == results.Length - 1 && QueryCompleted();
                    var trade = new TradeField();
                    trade.AccountID = item.AccountCode;
                    trade.ClientID = item.AssetNo;
                    trade.InstrumentID = item.StockCode;
                    trade.Symbol = item.StockCode;
                    trade.ExchangeID = UfxConvert.GetExchange(item.MarketNo);
                    trade.Date = item.DealDate;
                    trade.Time = item.DealTime;
                    trade.ID = IdGenerator.GetIdFromThirdRef(item.ThirdReff);
                    trade.TradeID = item.DealNo;
                    trade.Price = item.DealPrice;
                    trade.Qty = item.DealAmount;
                    trade.Commission = item.TotalFee;
                    trade.HedgeFlag = HedgeFlagType.Speculation;
                    trade.Side = UfxConvert.GetOrderSide(item.EntrustDirection);
                    trade.OpenClose = trade.Side == OrderSide.Buy ? OpenCloseType.Open : OpenCloseType.Close;
                    trade.ReserveChar64 = UfxClient;
                    _client.Spi.ProcessQryTrade(trade, last);
                }
            }
            else {
                _client.Spi.ProcessQryTrade(null, true);
            }
        }
        private void Process33003(Ufx33003Result[] results)
        {
            if (results.Length > 0) {
                for (int i = 0; i < results.Length; i++) {
                    var item = results[i];
                    var last = i == results.Length - 1 && QueryCompleted();
                    var trade = new TradeField();
                    trade.AccountID = item.AccountCode;
                    trade.ClientID = item.AssetNo;
                    trade.InstrumentID = item.StockCode;
                    trade.Symbol = item.StockCode;
                    trade.ExchangeID = UfxConvert.GetExchange(item.MarketNo);
                    trade.Date = item.DealDate;
                    trade.Time = item.DealTime;
                    trade.ID = IdGenerator.GetIdFromThirdRef(item.ThirdReff);
                    trade.TradeID = item.DealNo;
                    trade.Price = item.DealPrice;
                    trade.Qty = item.DealAmount;
                    trade.Commission = item.TotalFee;
                    trade.HedgeFlag = HedgeFlagType.Speculation;
                    trade.Side = UfxConvert.GetOrderSide(item.EntrustDirection);
                    trade.OpenClose = UfxConvert.GetTradeOpenClose(item.CloseType);
                    trade.ReserveChar64 = UfxClient;
                    _client.Spi.ProcessQryTrade(trade, last);
                }
            }
            else {
                _client.Spi.ProcessQryTrade(null, true);
            }
        }
        private void Process33004(Ufx33004Result[] results)
        {
            if (results.Length > 0) {
                for (int i = 0; i < results.Length; i++) {
                    var item = results[i];
                    var last = i == results.Length - 1 && QueryCompleted();
                    var trade = new TradeField();
                    trade.AccountID = item.AccountCode;
                    trade.ClientID = item.AssetNo;
                    trade.InstrumentID = item.StockCode;
                    trade.Symbol = item.StockCode;
                    trade.ExchangeID = UfxConvert.GetExchange(item.MarketNo);
                    trade.Date = item.DealDate;
                    trade.Time = item.DealTime;
                    trade.ID = IdGenerator.GetIdFromThirdRef(item.ThirdReff);
                    trade.TradeID = item.DealNo;
                    trade.Price = item.DealPrice;
                    trade.Qty = item.DealAmount;
                    trade.Commission = item.TotalFee;
                    trade.HedgeFlag = HedgeFlagType.Speculation;
                    trade.Side = UfxConvert.GetOrderSide(item.EntrustDirection);
                    trade.OpenClose = UfxConvert.GetTradeOpenClose(item.CloseType);
                    trade.ReserveChar64 = UfxClient;
                    _client.Spi.ProcessQryTrade(trade, last);
                }
            }
            else {
                _client.Spi.ProcessQryTrade(null, true);
            }
        }
        #endregion

        private void ProcessResponse(int functionId, object rsp)
        {
            _functionRunning.Dequeue();
            switch (functionId) {
                case UfxFuturesFunction.F30010:
                    Process30010((Ufx30010Result[])rsp);
                    break;
                case UfxAccountFunction.F35024:
                    Process35024((Ufx35024Result[])rsp);
                    break;
                case UfxAccountFunction.F35025:
                    Process35025((Ufx35025Result[])rsp);
                    break;
                case UfxFuturesFunction.F34003:
                    Process34003((Ufx34003Result[])rsp);
                    break;
                case UfxOptionsFunction.F34004:
                    Process34004((Ufx34004Result[])rsp);
                    break;
                case UfxSecuritiesFunction.F31001:
                    Process31001((Ufx31001Result[])rsp);
                    break;
                case UfxFuturesFunction.F31003:
                    Process31003((Ufx31003Result[])rsp);
                    break;
                case UfxOptionsFunction.F31004:
                    Process31004((Ufx31004Result[])rsp);
                    break;
                case UfxSecuritiesFunction.F32001:
                    Process32001((Ufx32001Result[])rsp);
                    break;
                case UfxFuturesFunction.F32003:
                    Process32003((Ufx32003Result[])rsp);
                    break;
                case UfxOptionsFunction.F32004:
                    Process32004((Ufx32004Result[])rsp);
                    break;
                case UfxSecuritiesFunction.F33001:
                    Process33001((Ufx33001Result[])rsp);
                    break;
                case UfxFuturesFunction.F33003:
                    Process33003((Ufx33003Result[])rsp);
                    break;
                case UfxOptionsFunction.F33004:
                    Process33004((Ufx33004Result[])rsp);
                    break;
            }
        }

        private void QueryAction(QueryEvent e)
        {
            if (e.Request) {
                _requestQueue.Enqueue(e.QueryType);
            }
            else {
                ProcessResponse(e.FunctionId, e.Response);
                ProcessFunctionPending();
            }
            if (QueryCompleted()) {
                ProcessQuery();
            }
        }

        public QueryManager(UfxClient client)
        {
            _client = client;
            _action = new ActionBlock<QueryEvent>(QueryAction);
        }

        public void Post(QueryType type)
        {
            _action.Post(new QueryEvent(type));
        }

        public void Post(int function, object response)
        {
            _action.Post(new QueryEvent(function, response));
        }

        public void Close()
        {
            _action.Complete();
            _action.Completion.Wait();
        }
    }
}